"""
Elasticsearch 客户端封装

提供ES连接管理、认证、SSL支持、连接池管理等功能。
"""

import ssl
import json
import base64
from typing import Dict, List, Optional, Any, Union
from elasticsearch import Elasticsearch
from elasticsearch.exceptions import (
    ConnectionError, AuthenticationException, AuthorizationException,
    NotFoundError, RequestError, TransportError
)

from .logger import get_module_logger
from .exceptions import (
    ESConnectionError, ESAuthenticationError, ESIndexNotFoundError,
    create_error_from_exception
)
from .retry import retry_on_es_error, ExponentialBackoffStrategy
from ..config.settings import get_settings

logger = get_module_logger(__name__)


class CurlDebugger:
    """ES 操作的 curl 命令调试器"""

    def __init__(self, hosts: List[str], username: str = None, password: str = None, api_key: str = None):
        """初始化 curl 调试器

        Args:
            hosts: ES 主机列表
            username: 用户名
            password: 密码
            api_key: API 密钥
        """
        self.hosts = hosts
        self.username = username
        self.password = password
        self.api_key = api_key
        self.base_url = hosts[0] if hosts else "http://localhost:9200"

        # 确保 URL 格式正确
        if not self.base_url.startswith(('http://', 'https://')):
            self.base_url = f"http://{self.base_url}"

    def _get_auth_header(self) -> str:
        """获取认证头部

        Returns:
            认证相关的 curl 参数
        """
        if self.api_key:
            return f'-H "Authorization: ApiKey {self.api_key}"'
        elif self.username and self.password:
            # 使用 Basic Auth
            credentials = base64.b64encode(f"{self.username}:{self.password}".encode()).decode()
            return f'-H "Authorization: Basic {credentials}"'
        else:
            return ""

    def _format_json_body(self, body: Any) -> str:
        """格式化 JSON 请求体

        Args:
            body: 请求体

        Returns:
            格式化的 JSON 字符串
        """
        if body is None:
            return ""

        if isinstance(body, (dict, list)):
            # 格式化 JSON，但保持紧凑以便在命令行中使用
            json_str = json.dumps(body, ensure_ascii=False, separators=(',', ':'))
            # 转义单引号和双引号以便在 shell 中使用
            json_str = json_str.replace('"', '\\"')
            return f'-d "{json_str}"'
        else:
            # 对于字符串类型的 body（如 bulk 操作）
            body_str = str(body).replace('"', '\\"').replace('\n', '\\n')
            return f'-d "{body_str}"'

    def _format_bulk_body(self, body: List[Dict[str, Any]]) -> str:
        """格式化 bulk 操作的请求体

        Args:
            body: bulk 操作的请求体列表

        Returns:
            格式化的 NDJSON 字符串
        """
        if not body:
            return ""

        # 将 bulk 操作转换为 NDJSON 格式
        ndjson_lines = []
        for item in body:
            ndjson_lines.append(json.dumps(item, ensure_ascii=False, separators=(',', ':')))

        ndjson_str = '\n'.join(ndjson_lines) + '\n'
        # 转义特殊字符
        ndjson_str = ndjson_str.replace('"', '\\"').replace('\n', '\\n')
        return f'-d "{ndjson_str}"'

    def print_curl_command(self, method: str, endpoint: str, body: Any = None, params: Dict[str, Any] = None, is_bulk: bool = False):
        """打印等效的 curl 命令

        Args:
            method: HTTP 方法
            endpoint: API 端点
            body: 请求体
            params: 查询参数
            is_bulk: 是否为 bulk 操作
        """
        # 构建完整 URL
        url = f"{self.base_url}/{endpoint.lstrip('/')}"

        # 添加查询参数
        if params:
            query_string = "&".join([f"{k}={v}" for k, v in params.items()])
            url = f"{url}?{query_string}"

        # 构建 curl 命令
        curl_parts = ["curl", f"-X {method.upper()}"]

        # 添加认证
        auth_header = self._get_auth_header()
        if auth_header:
            curl_parts.append(auth_header)

        # 添加内容类型头部
        if body is not None:
            if is_bulk:
                curl_parts.append('-H "Content-Type: application/x-ndjson"')
            else:
                curl_parts.append('-H "Content-Type: application/json"')

        # 添加请求体
        if body is not None:
            if is_bulk and isinstance(body, list):
                body_part = self._format_bulk_body(body)
            else:
                body_part = self._format_json_body(body)

            if body_part:
                curl_parts.append(body_part)

        # 添加 URL
        curl_parts.append(f'"{url}"')

        # 打印 curl 命令
        curl_command = " \\\n  ".join(curl_parts)
        logger.info(f"🐛 DEBUG - Equivalent curl command:\n{curl_command}")

        # 如果是 bulk 操作且数据量很大，提供简化版本
        if is_bulk and isinstance(body, list) and len(body) > 10:
            # 创建简化版本，只显示前几个操作
            sample_body = body[:4]  # 只取前4个操作作为示例
            sample_curl_parts = curl_parts[:-2]  # 移除原来的 body 和 URL
            sample_body_part = self._format_bulk_body(sample_body)
            if sample_body_part:
                sample_curl_parts.append(sample_body_part)
            sample_curl_parts.append(f'"{url}"')

            sample_command = " \\\n  ".join(sample_curl_parts)
            logger.info(f"🐛 DEBUG - Simplified curl command (first 2 operations):\n{sample_command}")
            logger.info(f"🐛 DEBUG - Note: Original bulk operation contains {len(body)} items")


class DebugESClient:
    """带调试功能的 ES 客户端装饰器"""

    def __init__(self, es_client, debug_mode: bool = False):
        """初始化调试客户端

        Args:
            es_client: 原始 ES 客户端
            debug_mode: 是否启用调试模式
        """
        self.es_client = es_client
        self.debug_mode = debug_mode
        self.curl_debugger = None

        if debug_mode:
            self.curl_debugger = CurlDebugger(
                hosts=es_client.hosts,
                username=es_client.username,
                password=es_client.password,
                api_key=es_client.api_key
            )
            logger.info("🐛 DEBUG MODE ENABLED - All ES operations will be logged as curl commands")

    def __getattr__(self, name):
        """代理所有属性访问到原始客户端"""
        return getattr(self.es_client, name)


class ESClient:
    """Elasticsearch 客户端封装类
    
    提供连接管理、认证、重试等功能。
    """
    
    def __init__(self, config: Optional[Dict[str, Any]] = None, debug_mode: bool = False):
        """初始化ES客户端

        Args:
            config: ES配置，如果为None则使用全局配置
            debug_mode: 是否启用调试模式
        """
        if config is None:
            settings = get_settings()
            config = settings.config.elasticsearch.dict()

        self.config = config
        self._client: Optional[Elasticsearch] = None
        self._cluster_info: Optional[Dict[str, Any]] = None
        self.debug_mode = debug_mode

        # 连接配置
        self.hosts = config.get('hosts', ['localhost:9200'])
        self.username = config.get('username')
        self.password = config.get('password')
        self.api_key = config.get('api_key')
        self.ssl_verify = config.get('ssl_verify', True)
        self.timeout = config.get('timeout', 30)
        self.request_timeout = config.get('request_timeout', 60)
        self.max_retries = config.get('max_retries', 3)

        # 初始化调试器
        self.curl_debugger = None
        if debug_mode:
            self.curl_debugger = CurlDebugger(
                hosts=self.hosts,
                username=self.username,
                password=self.password,
                api_key=self.api_key
            )
            logger.info("🐛 DEBUG MODE ENABLED - All ES operations will be logged as curl commands")

        logger.info(f"初始化ES客户端，连接到: {self.hosts}")
    
    @property
    def client(self) -> Elasticsearch:
        """获取ES客户端实例
        
        Returns:
            Elasticsearch客户端实例
        """
        if self._client is None:
            self._client = self._create_client()
        return self._client
    
    def _create_client(self) -> Elasticsearch:
        """创建ES客户端实例
        
        Returns:
            Elasticsearch客户端实例
            
        Raises:
            ESConnectionError: 连接失败
            ESAuthenticationError: 认证失败
        """
        try:
            # 构建连接参数
            client_config = {
                'hosts': self.hosts,
                'timeout': self.timeout,
                'max_retries': self.max_retries,
                'retry_on_timeout': True,
                'retry_on_status': [502, 503, 504],
                'verify_certs': False,
                'ssl_show_warn': False,
            }
            
            # 认证配置
            if self.api_key:
                client_config['api_key'] = self.api_key
            elif self.username and self.password:
                client_config['http_auth'] = (self.username, self.password)
            
            # SSL配置
            if any(host.startswith('https://') for host in self.hosts):
                client_config['use_ssl'] = True
                client_config['verify_certs'] = self.ssl_verify
                
                if not self.ssl_verify:
                    client_config['ssl_context'] = ssl.create_default_context()
                    client_config['ssl_context'].check_hostname = False
                    client_config['ssl_context'].verify_mode = ssl.CERT_NONE
            
            # 创建客户端
            client = Elasticsearch(**client_config)
            
            # 测试连接
            self._test_connection(client)
            
            logger.info("ES客户端创建成功")
            return client
            
        except AuthenticationException as e:
            raise ESAuthenticationError(f"ES认证失败: {e}", self.username)
        except ConnectionError as e:
            raise ESConnectionError(f"ES连接失败: {e}", str(self.hosts))
        except Exception as e:
            raise create_error_from_exception(e, "创建ES客户端失败")
    
    def _test_connection(self, client: Elasticsearch) -> None:
        """测试ES连接
        
        Args:
            client: ES客户端
            
        Raises:
            ESConnectionError: 连接测试失败
        """
        try:
            info = client.info()
            self._cluster_info = info
            logger.info(f"ES连接测试成功，集群: {info.get('cluster_name', 'unknown')}")
        except Exception as e:
            raise ESConnectionError(f"ES连接测试失败: {e}")
    
    @retry_on_es_error(max_attempts=3, strategy=ExponentialBackoffStrategy())
    def search(self, index: str, body: Dict[str, Any], **kwargs) -> Dict[str, Any]:
        """执行搜索查询

        Args:
            index: 索引名称
            body: 查询体
            **kwargs: 其他参数

        Returns:
            搜索结果

        Raises:
            ESIndexNotFoundError: 索引不存在
            ESConnectionError: 连接错误
        """
        try:
            logger.debug(f"执行搜索查询: {index}")

            # 调试模式：打印 curl 命令
            if self.debug_mode and self.curl_debugger:
                params = {}
                if 'request_timeout' in kwargs:
                    params['timeout'] = f"{kwargs['request_timeout']}s"
                if 'scroll' in kwargs:
                    params['scroll'] = kwargs['scroll']
                if 'size' in kwargs:
                    params['size'] = kwargs['size']

                self.curl_debugger.print_curl_command(
                    method="POST",
                    endpoint=f"{index}/_search",
                    body=body,
                    params=params
                )

            # 设置默认超时
            if 'request_timeout' not in kwargs:
                kwargs['request_timeout'] = self.request_timeout

            result = self.client.search(index=index, body=body, **kwargs)

            logger.debug(f"搜索完成，返回 {result['hits']['total']['value']} 条结果")
            return result

        except NotFoundError:
            raise ESIndexNotFoundError(index)
        except (ConnectionError, TransportError) as e:
            raise ESConnectionError(f"搜索查询失败: {e}")
        except Exception as e:
            raise create_error_from_exception(e, f"搜索查询失败: {index}")
    
    @retry_on_es_error(max_attempts=3)
    def scroll(self, scroll_id: str, scroll: str = '5m') -> Dict[str, Any]:
        """执行滚动查询

        Args:
            scroll_id: 滚动ID
            scroll: 滚动超时时间

        Returns:
            滚动结果
        """
        try:
            # 调试模式：打印 curl 命令
            if self.debug_mode and self.curl_debugger:
                body = {
                    "scroll": scroll,
                    "scroll_id": scroll_id
                }
                self.curl_debugger.print_curl_command(
                    method="POST",
                    endpoint="_search/scroll",
                    body=body
                )

            return self.client.scroll(scroll_id=scroll_id, scroll=scroll)
        except Exception as e:
            raise create_error_from_exception(e, "滚动查询失败")

    @retry_on_es_error(max_attempts=3)
    def clear_scroll(self, scroll_id: str) -> None:
        """清理滚动上下文

        Args:
            scroll_id: 滚动ID
        """
        try:
            # 调试模式：打印 curl 命令
            if self.debug_mode and self.curl_debugger:
                body = {
                    "scroll_id": [scroll_id] if isinstance(scroll_id, str) else scroll_id
                }
                self.curl_debugger.print_curl_command(
                    method="DELETE",
                    endpoint="_search/scroll",
                    body=body
                )

            self.client.clear_scroll(scroll_id=scroll_id)
        except Exception as e:
            logger.warning(f"清理滚动上下文失败: {e}")
    
    @retry_on_es_error(max_attempts=3)
    def get_mapping(self, index: str) -> Dict[str, Any]:
        """获取索引映射

        Args:
            index: 索引名称

        Returns:
            索引映射
        """
        try:
            logger.debug(f"获取索引映射: {index}")

            # 调试模式：打印 curl 命令
            if self.debug_mode and self.curl_debugger:
                self.curl_debugger.print_curl_command(
                    method="GET",
                    endpoint=f"{index}/_mapping"
                )

            result = self.client.indices.get_mapping(index=index)
            return result
        except NotFoundError:
            raise ESIndexNotFoundError(index)
        except Exception as e:
            raise create_error_from_exception(e, f"获取索引映射失败: {index}")
    
    @retry_on_es_error(max_attempts=3)
    def get_settings(self, index: str) -> Dict[str, Any]:
        """获取索引设置
        
        Args:
            index: 索引名称
            
        Returns:
            索引设置
        """
        try:
            logger.debug(f"获取索引设置: {index}")
            result = self.client.indices.get_settings(index=index)
            return result
        except NotFoundError:
            raise ESIndexNotFoundError(index)
        except Exception as e:
            raise create_error_from_exception(e, f"获取索引设置失败: {index}")
    
    def index_exists(self, index: str) -> bool:
        """检查索引是否存在

        Args:
            index: 索引名称

        Returns:
            索引是否存在
        """
        try:
            # 调试模式：打印 curl 命令
            if self.debug_mode and self.curl_debugger:
                self.curl_debugger.print_curl_command(
                    method="HEAD",
                    endpoint=index
                )

            return self.client.indices.exists(index=index)
        except Exception as e:
            logger.warning(f"检查索引存在性失败: {e}")
            return False
    
    @retry_on_es_error(max_attempts=3)
    def create_index(self, index: str, body: Optional[Dict[str, Any]] = None) -> bool:
        """创建索引

        Args:
            index: 索引名称
            body: 索引配置

        Returns:
            是否创建成功
        """
        try:
            logger.info(f"创建索引: {index}")

            if self.index_exists(index):
                logger.warning(f"索引已存在: {index}")
                return True

            # 调试模式：打印 curl 命令
            if self.debug_mode and self.curl_debugger:
                self.curl_debugger.print_curl_command(
                    method="PUT",
                    endpoint=index,
                    body=body or {}
                )

            self.client.indices.create(index=index, body=body or {})
            logger.info(f"索引创建成功: {index}")
            return True

        except Exception as e:
            logger.error(f"创建索引失败: {index}, {e}")
            return False
    
    @retry_on_es_error(max_attempts=3)
    def bulk(self, body: List[Dict[str, Any]], **kwargs) -> Dict[str, Any]:
        """批量操作

        Args:
            body: 批量操作体
            **kwargs: 其他参数

        Returns:
            批量操作结果
        """
        try:
            # 调试模式：打印 curl 命令
            if self.debug_mode and self.curl_debugger:
                params = {}
                if 'request_timeout' in kwargs:
                    params['timeout'] = f"{kwargs['request_timeout']}s"
                if 'refresh' in kwargs:
                    params['refresh'] = kwargs['refresh']

                self.curl_debugger.print_curl_command(
                    method="POST",
                    endpoint="_bulk",
                    body=body,
                    params=params,
                    is_bulk=True
                )

            if 'request_timeout' not in kwargs:
                kwargs['request_timeout'] = self.request_timeout

            result = self.client.bulk(body=body, **kwargs)

            # 检查错误
            if result.get('errors'):
                error_count = sum(1 for item in result['items']
                                if any(op.get('error') for op in item.values()))
                logger.warning(f"批量操作包含 {error_count} 个错误")

            return result

        except Exception as e:
            raise create_error_from_exception(e, "批量操作失败")
    
    def get_cluster_info(self) -> Dict[str, Any]:
        """获取集群信息
        
        Returns:
            集群信息
        """
        if self._cluster_info is None:
            try:
                self._cluster_info = self.client.info()
            except Exception as e:
                logger.warning(f"获取集群信息失败: {e}")
                return {}
        
        return self._cluster_info or {}
    
    def get_cluster_health(self) -> Dict[str, Any]:
        """获取集群健康状态

        Returns:
            集群健康信息
        """
        try:
            # 调试模式：打印 curl 命令
            if self.debug_mode and self.curl_debugger:
                self.curl_debugger.print_curl_command(
                    method="GET",
                    endpoint="_cluster/health"
                )

            return self.client.cluster.health()
        except Exception as e:
            logger.warning(f"获取集群健康状态失败: {e}")
            return {}

    @retry_on_es_error(max_attempts=3)
    def get_index_stats(self, index: str) -> Dict[str, Any]:
        """获取索引统计信息

        Args:
            index: 索引名称

        Returns:
            索引统计信息
        """
        try:
            logger.debug(f"获取索引统计: {index}")

            # 调试模式：打印 curl 命令
            if self.debug_mode and self.curl_debugger:
                self.curl_debugger.print_curl_command(
                    method="GET",
                    endpoint=f"{index}/_stats"
                )

            result = self.client.indices.stats(index=index)
            return result
        except NotFoundError:
            raise ESIndexNotFoundError(index)
        except Exception as e:
            raise create_error_from_exception(e, f"获取索引统计失败: {index}")

    @retry_on_es_error(max_attempts=3)
    def delete_by_query(self, index: str, body: Dict[str, Any], **kwargs) -> Dict[str, Any]:
        """按查询删除文档

        Args:
            index: 索引名称
            body: 删除查询体
            **kwargs: 其他参数

        Returns:
            删除结果
        """
        try:
            logger.info(f"执行按查询删除: {index}")

            # 调试模式：打印 curl 命令
            if self.debug_mode and self.curl_debugger:
                params = {}
                if 'request_timeout' in kwargs:
                    params['timeout'] = f"{kwargs['request_timeout']}s"
                if 'wait_for_completion' in kwargs:
                    params['wait_for_completion'] = str(kwargs['wait_for_completion']).lower()
                if 'refresh' in kwargs:
                    params['refresh'] = str(kwargs['refresh']).lower()
                if 'conflicts' in kwargs:
                    params['conflicts'] = kwargs['conflicts']

                self.curl_debugger.print_curl_command(
                    method="POST",
                    endpoint=f"{index}/_delete_by_query",
                    body=body,
                    params=params
                )

            # 设置默认超时
            if 'request_timeout' not in kwargs:
                kwargs['request_timeout'] = self.request_timeout

            result = self.client.delete_by_query(index=index, body=body, **kwargs)

            deleted_count = result.get('deleted', 0)
            logger.info(f"删除完成，共删除 {deleted_count} 个文档")
            return result

        except NotFoundError:
            raise ESIndexNotFoundError(index)
        except (ConnectionError, TransportError) as e:
            raise ESConnectionError(f"按查询删除失败: {e}")
        except Exception as e:
            raise create_error_from_exception(e, f"按查询删除失败: {index}")

    @retry_on_es_error(max_attempts=3)
    def count(self, index: str, body: Optional[Dict[str, Any]] = None, **kwargs) -> Dict[str, Any]:
        """统计文档数量

        Args:
            index: 索引名称
            body: 查询体
            **kwargs: 其他参数

        Returns:
            统计结果
        """
        try:
            logger.debug(f"统计文档数量: {index}")

            # 调试模式：打印 curl 命令
            if self.debug_mode and self.curl_debugger:
                params = {}
                if 'request_timeout' in kwargs:
                    params['timeout'] = f"{kwargs['request_timeout']}s"

                endpoint = f"{index}/_count"
                self.curl_debugger.print_curl_command(
                    method="POST" if body else "GET",
                    endpoint=endpoint,
                    body=body,
                    params=params
                )

            # 设置默认超时
            if 'request_timeout' not in kwargs:
                kwargs['request_timeout'] = self.request_timeout

            result = self.client.count(index=index, body=body, **kwargs)

            count = result.get('count', 0)
            logger.debug(f"文档统计完成: {count} 个文档")
            return result

        except NotFoundError:
            raise ESIndexNotFoundError(index)
        except (ConnectionError, TransportError) as e:
            raise ESConnectionError(f"文档统计失败: {e}")
        except Exception as e:
            raise create_error_from_exception(e, f"文档统计失败: {index}")
    
    def close(self) -> None:
        """关闭客户端连接"""
        if self._client:
            try:
                self._client.transport.close()
                logger.info("ES客户端连接已关闭")
            except Exception as e:
                logger.warning(f"关闭ES客户端连接失败: {e}")
            finally:
                self._client = None
    
    def __enter__(self):
        """上下文管理器入口"""
        return self
    
    def __exit__(self, exc_type, exc_val, exc_tb):
        """上下文管理器出口"""
        self.close()


def create_es_client(config: Optional[Dict[str, Any]] = None, debug_mode: bool = False) -> ESClient:
    """创建ES客户端实例

    Args:
        config: ES配置
        debug_mode: 是否启用调试模式

    Returns:
        ES客户端实例
    """
    return ESClient(config, debug_mode=debug_mode)
